//
// Created on 2024/4/17.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".


#include "napi/native_api.h"
#include <dlfcn.h>
#include <malloc.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "hilog/log.h"
#include "rtmp/libjsrtmp/jsrtmpstruct.h"

static napi_value Subtraction(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = {NULL};

    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sub;
    napi_create_double(env, value0 - value1, &sub);

    return sub;
}

static napi_value Open(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = {NULL};

    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    napi_status url_status;
    char *url = (char *)malloc(sizeof(char));
    size_t url_length;
    url_status = napi_get_value_string_utf8(env, args[0], url, sizeof(url), &url_length);

    bool isPublishMode;
    isPublishMode = napi_get_value_bool(env, args[1], &isPublishMode);
    OH_LOG_ERROR(LOG_APP, "url====%s", url);
    if (url_status != napi_ok) {
        napi_throw_error(env, NULL, "Failed to get url value");
        return NULL;
    }
    napi_value result;
    void *handle = dlopen("librertmp.so", RTLD_LAZY);
    if (!handle) {
        // 加载失败
        char *fail = "推流库加载失败";
        napi_create_string_utf8(env, fail, strlen(fail), &result);
        // 将加载失败原因甩到ArkTs层，可以在try catch中获捕到
        napi_throw_error(env, NULL, dlerror());
        return result;
    }

    typedef RTMP *(*RTMP_Alloc_Func)(void);
    RTMP_Alloc_Func RTMP_Alloc = (RTMP_Alloc_Func)dlsym(handle, "RTMP_Alloc");
    RTMP *rtmp = RTMP_Alloc();

    if (rtmp == NULL) {
        dlclose(handle);
        return NULL;
    }

    typedef void (*RTMP_Init_Func)(RTMP *);
    RTMP_Init_Func RTMP_Init = (RTMP_Init_Func)dlsym(handle, "RTMP_Init");
    RTMP_Init(rtmp);

    typedef int (*RTMP_SetupURL_Func)(RTMP *, char *);
    RTMP_SetupURL_Func RTMP_SetupURL = (RTMP_SetupURL_Func)dlsym(handle, "RTMP_SetupURL");
    int ret = RTMP_SetupURL(rtmp, url);

    typedef void (*RTMP_Free_Func)(RTMP *);
    RTMP_Free_Func RTMP_Free = (RTMP_Free_Func)dlsym(handle, "RTMP_Free");
    if (!ret) {
        RTMP_Free(rtmp);
        dlclose(handle);
        rtmp = NULL;
        return NULL;
    }

    if (isPublishMode) {
        RTMP_Free(rtmp);
    }

    typedef int (*RTMP_Connect_Func)(RTMP *, RTMPPacket *);
    RTMP_Connect_Func RTMP_Connect = (RTMP_Connect_Func)dlsym(handle, "RTMP_Connect");
    ret = RTMP_Connect(rtmp, NULL);
    if (!ret) {
        RTMP_Free(rtmp);
        dlclose(handle);
        rtmp = NULL;
        return NULL;
    }
    typedef int (*RTMP_ConnectStream_Func)(RTMP *, int);
    RTMP_ConnectStream_Func RTMP_ConnectStream = (RTMP_ConnectStream_Func)dlsym(handle, "RTMP_ConnectStream");
    ret = RTMP_ConnectStream(rtmp, 0);
    if (!ret) {
        typedef int (*RTMP_Close_Func)(RTMP *);
        RTMP_Close_Func RTMP_Close = (RTMP_Close_Func)dlsym(handle, "RTMP_Close");
        ret = RTMP_ConnectStream(rtmp, 0);
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
        dlclose(handle);
        rtmp = NULL;
        return NULL;
    }

    free(url);
    dlclose(handle);

    napi_value result1;
    napi_create_int64(env, (int64_t)rtmp, &result1);

    return result1;
}

static napi_value Read(napi_env env, napi_callback_info info) {
    size_t argc = 4;
    napi_value args[4] = {NULL};

    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    napi_valuetype valuetype2;
    napi_typeof(env, args[2], &valuetype2);

    napi_valuetype valuetype3;
    napi_typeof(env, args[3], &valuetype3);

    int64_t rtmp;
    napi_get_value_int64(env, args[0], &rtmp);

    napi_value data_;
    data_ = args[1];

    int offset;
    napi_get_value_int32(env, args[2], &offset);

    int size;
    napi_get_value_int32(env, args[3], &size);

    char *data = malloc(size * sizeof(char));

    napi_value result;
    void *handle = dlopen("librertmp.so", RTLD_LAZY);
    if (!handle) {
        // 加载失败
        char *fail = "推流库加载失败";
        napi_create_string_utf8(env, fail, strlen(fail), &result);
        // 将加载失败原因甩到ArkTs层，可以在try catch中获捕到
        napi_throw_error(env, NULL, dlerror());
        return result;
    }

    typedef int (*RTMP_Read_Func)(RTMP *, char *, int);
    RTMP_Read_Func RTMP_Read = (RTMP_Read_Func)dlsym(handle, "RTMP_Read");
    int readCount = RTMP_Read((RTMP *)rtmp, data, size);

    if (readCount > 0) {
        void *bufferData;
        size_t bufferLength;
        napi_typedarray_type type;
        napi_status status = napi_get_typedarray_info(env, data_, &type, &bufferLength, &bufferData, NULL, NULL);
        if (status != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "Failed to get buffer info====%d", status);
            free(data);
            dlclose(handle);
            return NULL;
        }
        memcpy((char *)bufferData + offset, data, size);
    }

    free(data);

    dlclose(handle);
    napi_value result1;
    napi_create_int32(env, readCount, &result1);
    return result1;
}

static napi_value Write(napi_env env, napi_callback_info info) {
    OH_LOG_ERROR(LOG_APP, "start write");
    size_t argc = 5;
    napi_value args[5] = {NULL};
    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype2;
    napi_typeof(env, args[2], &valuetype2);

    napi_valuetype valuetype3;
    napi_typeof(env, args[3], &valuetype3);

    napi_valuetype valuetype4;
    napi_typeof(env, args[4], &valuetype4);

    int64_t rtmp;
    napi_get_value_int64(env, args[0], &rtmp);

    bool is_typedarray;
    napi_is_typedarray(env, args[1], &is_typedarray);
    if (!is_typedarray) {
        // 如果参数不是 TypedArray，则抛出错误或者返回一个错误值
        napi_throw_error(env, NULL, "Expected a TypedArray");
        return NULL;
    }

    void *buffer;
    size_t length;
    napi_typedarray_type type;
    napi_get_typedarray_info(env, args[1], &type, &length, &buffer, NULL, NULL);

    int offset, size, ts;
    napi_get_value_int32(env, args[2], &offset);

    napi_get_value_int32(env, args[3], &size);

    napi_get_value_int32(env, args[4], &ts);

    napi_value result;
    void *handle = dlopen("librertmp.so", RTLD_LAZY);
    if (!handle) {
        // 加载失败
        char *fail = "推流库加载失败";
        napi_create_string_utf8(env, fail, strlen(fail), &result);
        // 将加载失败原因甩到ArkTs层，可以在try catch中获捕到
        napi_throw_error(env, NULL, dlerror());
        return result;
    }

    RTMPPacket *packet = (RTMPPacket *)malloc(sizeof(RTMPPacket));

    typedef int (*TMPPacket_Alloc_Func)(RTMPPacket *, int);
    TMPPacket_Alloc_Func TMPPacket_Alloc = (TMPPacket_Alloc_Func)dlsym(handle, "TMPPacket_Alloc");
    TMPPacket_Alloc(packet, size);

    typedef int (*TMPPacket_Reset_Func)(RTMPPacket *);
    TMPPacket_Reset_Func TMPPacket_Reset = (TMPPacket_Reset_Func)dlsym(handle, "TMPPacket_Reset");
    TMPPacket_Reset(packet);

    if (type == RTMP_PACKET_TYPE_INFO) { // metadata
        packet->m_nChannel = 0x03;
    } else if (type == RTMP_PACKET_TYPE_VIDEO) { // video
        packet->m_nChannel = 0x04;
    } else if (type == RTMP_PACKET_TYPE_AUDIO) { // audio
        packet->m_nChannel = 0x05;
    } else {
        packet->m_nChannel = -1;
    }
    packet->m_nInfoField2 = ((RTMP *)rtmp)->m_stream_id;

    OH_LOG_ERROR(LOG_APP, "write data type: %d, ts %d", type, ts);

    memcpy(packet->m_body, buffer, size);
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
    packet->m_hasAbsTimestamp = FALSE;
    packet->m_nTimeStamp = ts;
    packet->m_packetType = type;
    packet->m_nBodySize = size;


    typedef int (*RTMP_SendPacket_Func)(RTMP *, RTMPPacket *, int);
    RTMP_SendPacket_Func RTMP_SendPacket = (RTMP_SendPacket_Func)dlsym(handle, "RTMP_SendPacket");
    int ret = RTMP_SendPacket((RTMP *)rtmp, packet, 0);

    typedef int (*RTMPPacket_Free_Func)(RTMPPacket *);
    RTMPPacket_Free_Func RTMPPacket_Free = (RTMPPacket_Free_Func)dlsym(handle, "RTMPPacket_Free");
    RTMPPacket_Free(packet);
    free(packet);

    dlclose(handle);

    if (!ret) {
        OH_LOG_ERROR(LOG_APP, "end write error %d", 0);
        return 0;
    } else {
        OH_LOG_ERROR(LOG_APP, "end write success");
        return 0;
    }
}

static napi_value Close(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {NULL};

    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);
    int64_t rtmp;
    napi_get_value_int64(env, args[0], &rtmp);

    napi_value result;
    void *handle = dlopen("librertmp.so", RTLD_LAZY);
    if (!handle) {
        // 加载失败
        char *fail = "推流库加载失败";
        napi_create_string_utf8(env, fail, strlen(fail), &result);
        // 将加载失败原因甩到ArkTs层，可以在try catch中获捕到
        napi_throw_error(env, NULL, dlerror());
        return result;
    }
    
    typedef int (*RTMP_Close_Func)(RTMP *);
    RTMP_Close_Func RTMP_Close = (RTMP_Close_Func)dlsym(handle, "RTMP_Close");
    RTMP_Close((RTMP *)rtmp);

    typedef void (*RTMP_Free_Func)(RTMP *);
    RTMP_Free_Func RTMP_Free = (RTMP_Free_Func)dlsym(handle, "RTMP_Free");
    RTMP_Free((RTMP *)rtmp);

    napi_value result1;
    napi_create_int32(env, 0, &result1);
    return result;
}

static napi_value GetIpAddr(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {NULL};

    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);
    long rtmp;
    napi_get_value_int64(env, args[0], &rtmp);
    napi_value result;
    if (rtmp != 0) {
        RTMP *r = (RTMP *)rtmp;
        napi_create_string_utf8(env, r->ipaddr, NAPI_AUTO_LENGTH, &result);
    } else {
        napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &result);
    }
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {{"subtraction", NULL, Subtraction, NULL, NULL, NULL, napi_default, NULL},
                                       {"open", NULL, Open, NULL, NULL, NULL, napi_default, NULL},
                                       {"read", NULL, Read, NULL, NULL, NULL, napi_default, NULL},
                                       {"write", NULL, Write, NULL, NULL, NULL, napi_default, NULL},
                                       {"close", NULL, Close, NULL, NULL, NULL, napi_default, NULL},
                                       {"getIpAddr", NULL, GetIpAddr, NULL, NULL, NULL, napi_default, NULL}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = NULL,
    .nm_register_func = Init,
    .nm_modname = "rertmp",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

__attribute__((constructor)) void RegisterRertmpModule(void) { napi_module_register(&demoModule); }